/*
 * Created by LiuYou on 2020/10/22.
 *
 */

#ifndef SEQUENCELIST_HPP
#define SEQUENCELIST_HPP


#include <iostream>
#include <fstream>
#include <random>
#include "../Questions.hpp"


namespace ly {


    template<typename Tp>
    class SequenceList {
    public:
        int halfSearch(int difficulty) const {
            // 初始化查找区间
            int mid{0};
            int low{1};

            // 长度减 1 为难度最大的题目所在的索引
            int high{static_cast<int>(getLength() - 1 )};

            // 当区间存在时
            while (low <= high) {
                mid = (low + high) / 2;
                if (difficulty < element[mid].getDifficulty()) {
                    high = mid - 1;
                } else if (difficulty > element[mid].getDifficulty()) {
                    low = mid + 1;
                } else {
                    // 查询成功，返回题目的序号
                    return mid;
                }
            }
            // 查询失败
            return 0;
        }

#ifdef VERSION_PTR

        SequenceList& addQuestionsByReference(Questions* questions) {
            element[length] = questions;
            ++length;
            if (length == maxSize) {
                reSize();
            }
            return *this;
        }

#endif

        SequenceList& addQuestions(const Questions& questions) {
            element[length] = questions;
            ++length;
            if (length == maxSize) {
                reSize();
            }
            return *this;
        }


        SequenceList& addIntegerElement(int value) {
            element[length] = value;
            ++length;
            if (length == maxSize) {
                reSize();
            }
            return *this;
        }

        void updateQuestions() {
            std::cout << std::endl << "题目查询(更新题目)，输入题目难度系数进行修改更新: ";
            int difficulty{0};
            std::cin >> difficulty;
            int index{0};
            for (int i = 0; i < length; ++i) {
                if (element[i].getDifficulty() == difficulty) {
                    std::cout << i << "> " << element[i] << std::endl;
                    ++index;
                }
            }
            std::cout << "题目难度: " << difficulty << ", 题目类型: " << element[0].getQuestionType() << ", 题目数量: " << index
                      << std::endl;
            int theNumberOfQuestion{0};
            std::cout << "想要更新的题目: ";
            std::cin >> theNumberOfQuestion;
            std::cout << "修改题目难度: ";
            std::cin >> difficulty;
            std::cin.get();
            element[theNumberOfQuestion].setDifficulty(difficulty);
            std::cout << "修改题目: ";
            std::string theContentOfQuestion;
            std::getline(std::cin, theContentOfQuestion);
            element[theNumberOfQuestion].setQuestion("题目: " + theContentOfQuestion);
        }

#ifdef VERSION_PTR

        /*
         * 划分，为之后的快速排序做准备。
         */
        int partition(int first, int last) {
            int i{first};
            int j{last};
            ly::Questions temp;
            while (i < j) {
                while (i < j && getElement()[i].getDifficulty() <= getElement()[j].getDifficulty()) {
                    --j;
                }
                if (i < j) {
                    temp = getElement()[i];
                    getElement()[i] = getElement()[j];
                    getElement()[j] = temp;
                    ++i;
                }
                while (i < j && getElement()[i].getDifficulty() <= getElement()[j].getDifficulty()) {
                    ++i;
                }
                if (i < j) {
                    temp = getElement()[i];
                    getElement()[i] = getElement()[j];
                    getElement()[j] = temp;
                    --j;
                }
            }
            return i;
        }

        /*
         * 快速排序。
         */
        void quickSort(int first, int last) {
            if (first >= last) {
                return;
            } else {
                int pivot{partition(first, last)};
                quickSort(first, pivot - 1);
                quickSort(pivot + 1, last);
            }
        }

#endif

        void queryQuestionsByDifficulty() const {
            std::cout << std::endl << "题目查询，输入题目难度系数进行查询: ";
            int difficulty{0};
            std::cin >> difficulty;
            int index{0};
            for (int i = 0; i < length; ++i) {
                if (element[i].getDifficulty() == difficulty) {
                    std::cout << i << "> " << element[i] << std::endl;
                    ++index;
                }
            }
        }

        /*
         * 存储问题的文件的信息。
         */
        void infoOfQuestionsFile() const {
            for (int i = 0; i < length; ++i) {
                std::cout << i << "> " << element[i] << std::endl;
            }
            std::cout << "题目类型: " << element[1].getQuestionType() << ", 题目数量: " << length << std::endl;
        }

        /*
         * 向指定文件写入题目进行存储。
         */
        void writeQuestionsToFile(std::string filePath, int questionType) {
            std::ofstream outputFileStream;
            const char* file[5]{"CalculationQuestions", "ApplicationQuestions", "CompletionQuestions",
                                "ChoiceQuestions", "BrainQuestions"};
            outputFileStream.open(filePath + file[questionType - 1]);
            for (int i = 0; i < length; ++i) {
                outputFileStream << element[i].getDifficulty() << std::endl << "---"
                                 << std::endl
                                 << element[i].getQuestionType() << std::endl << std::endl
                                 << element[i].getQuestion() << std::endl << "----"
                                 << std::endl
                                 << std::endl;
            }
            outputFileStream.close();
        }

        /*
         * 从文件中读取题目。
         */
        void readQuestionsFromFile(const std::string& fileName) {
            // 文件输出流
            std::ifstream inputFileStream;
            inputFileStream.open(fileName);
            std::string line;
            int index{0};
            // 读取文件
            while (std::getline(inputFileStream, line)) {
                if (line == "1" || line == "2" || line == "3" || line == "4" || line == "5") {
                    element[index].setDifficulty(std::stoi(line));
                } else if (line == "---") {
                    std::getline(inputFileStream, line);
                    /// 产生内存泄漏，这次这个错误很头疼！！！！
                    /// 原因: 使用 std::unique_ptr不能有赋值或者拷贝。std::unique_ptr独享内存。
                    /// 不对，我也不知道具体是为什么。
                    (element[index]).setQuestionType(line);

                    // 处理空行
                    while (std::getline(inputFileStream, line)) {
                        if (!line.empty()) {
                            break;
                        }
                    }
                    element[index].setQuestion(line);
                    ++index;
                }
                length = index;
                if (length == maxSize) {
                    reSize();
                }
            }
            inputFileStream.close();
        }

    private:
        /*
         * 当动态内存使用完后，将表的大小扩容一倍。
         */
        void reSize() {
            Tp* ptr = element;
            maxSize *= 2;
            element = new Tp[maxSize];
            for (int i = 0; i < length; ++i) {
                element[i] = ptr[i];
            }
            delete[] ptr;
        }

    public:
        /*
         * 添加问题到文件末尾。
         */
        std::string addQuestionToFile(const char** file, int questionType) {
            std::ofstream outputFileStream;
            // 将新的题目添加到文件末尾
            outputFileStream.open(static_cast<std::string>("QuestionRepository/") + file[questionType - 1],
                                  std::ofstream::app);
            std::cout << std::endl << "下面增加题目，题目难度: ";
            int difficulty{0};
            std::cin >> difficulty;
            std::cout << "题目类型: ";
            std::string theTypeOfQuestion;
            std::cin >> theTypeOfQuestion;
            // 读取回车字符
            std::cin.get();
            std::cout << "题目: ";
            std::string question;
            std::getline(std::cin, question);

            outputFileStream << std::endl << difficulty << std::endl << "---" << std::endl
                             << theTypeOfQuestion << std::endl << std::endl
                             << "题目: " << question << std::endl << "----" << std::endl;
            outputFileStream.close();
            return file[questionType - 1];
        }

        /*
         * 删除题目指定的题目。
         */
        SequenceList& remove(int theNumberOfQuestion) {
            Tp value{element[theNumberOfQuestion]};
            for (int i = theNumberOfQuestion; i < length; ++i) {
                if (i == length - 1) {
                    break;
                }
                element[i] = element[i + 1];
            }

            // 题目减一
            --length;
            return *this;
        }

        /*
         * 删除题目。
         */
        SequenceList& remove() {
            std::cout << std::endl << "下面删除题目，输入题目难度系数进行删除: ";
            int difficulty{0};
            std::cin >> difficulty;
            int index{0};
            for (int i = 0; i < length; ++i) {
                if (element[i].getDifficulty() == difficulty) {
                    std::cout << i << "> " << element[i] << std::endl;
                    ++index;
                }
            }
            std::cout << "该难度系数共有 " << index << " 道试题" << std::endl;
            int theNumberOfQuestion{0};
            std::cout << "想要移除的题目: ";
            std::cin >> theNumberOfQuestion;

            Tp value{element[theNumberOfQuestion]};
            for (int i = theNumberOfQuestion; i < length; ++i) {
                if (i == length - 1) {
                    break;
                }
                element[i] = element[i + 1];
            }

            // 题目减一
            --length;
            return *this;
        }

        Tp* getElement() const {
            return element;
        }

        std::size_t getLength() const {
            return length;
        }

        std::size_t getMaxSize() const {
            return maxSize;
        }

        virtual ~SequenceList() {
            delete[] element;
        }

        SequenceList() = default;

    private:
        Tp* element{new Tp[10]()};
        std::size_t length{0};
        std::size_t maxSize{10};
    };

    // 使用函数模板
    // 方便后续对Questions类进行子类的设计、类型参数可以是非类类型既内置类型如int等好处。

    /*
     * 划分，为之后的快速排序做准备。
     */
    template<typename T>
    int partition(int first, int last, ly::SequenceList<T>& questions) {
        // 划分的区间
        int i{first};
        int j{last};
        // temp是进行交换赋值的中间临时对象。
        T temp;
        while (i < j) {
            // 右侧扫描
            while (i < j && questions.getElement()[i].getDifficulty() <=
                            questions.getElement()[j].getDifficulty()) {
                --j;
            }
            // 两个题目进行交换，左侧区间临界值加一
            if (i < j) {
                temp = questions.getElement()[i];
                questions.getElement()[i] = questions.getElement()[j];
                questions.getElement()[j] = temp;
                ++i;
            }
            // 左侧扫描
            while (i < j && questions.getElement()[i].getDifficulty() <=
                            questions.getElement()[j].getDifficulty()) {
                ++i;
            }
            // 两个题目进行交换，右侧区间临界值减一
            if (i < j) {
                temp = questions.getElement()[i];
                questions.getElement()[i] = questions.getElement()[j];
                questions.getElement()[j] = temp;
                --j;
            }
        }
        // i为轴值记录的最终位置
        return i;
    }


    /*
     * 快速排序。
     */
    template<typename T>
    void quickSort(int first, int last, ly::SequenceList<T>& questions) {
        // 区间长度为1，递归结束
        if (first >= last) {
            return;
        } else {
            // 进行一次划分
            int pivot{partition(first, last, questions)};
            // 递归对左侧子区间进行快排
            quickSort(first, pivot - 1, questions);
            // 递归对右侧子区间进行快排
            quickSort(pivot + 1, last, questions);
        }
    }

}


namespace ly {

    /*
     * 产生随机数函数。
     *
     * @return 返回一个均匀分布的随机数。
     */
    int randomNumber() {
        static std::uniform_int_distribution<int> uniformIntDistribution(1, 5);
        int randomInteger;
        static std::default_random_engine defaultRandomEngine(randomInteger);
        return uniformIntDistribution(defaultRandomEngine);
    }

    /*
     * 使用二分查找，如果没有那就 flag 加一，
     * 如果有那就将有的那个输出到 MathTest/Test 当中。
     *
     */
    auto generationTest(std::ofstream& outputFileStream, ly::SequenceList<ly::Questions>& questions,
                        const char** file) -> double {
        outputFileStream.open("MathTest/Test");
        outputFileStream << "\t\t\t\t2 0 2 0 年 小 学 提 升 计 划 统 一 考  试" << std::endl
                         << "\t\t\t\t\t\t\t\t数 学" << std::endl
                         << "\t\t\t\t   本试卷满分 150 分,考试时间 120 分钟" << std::endl << std::endl;
        const char* theTypeOfQuestions[5]{"一、计算题: 本题共 5 小题, 每小题 6 分, 共30分.",
                                          "二、应用题: 本题共 5 小题, 每小题 6 分, 共30分.",
                                          "三、填空题: 本题共 5 小题, 每小题 6 分, 共30分.",
                                          "四、判断题: 本题共 5 小题, 每小题 6 分, 共30分.",
                                          "五、脑筋急转弯题: 本题共 5 小题, 每小题 6 分, 共30分."};
        int degreeOfDifficulty{0};
        ly::SequenceList<int> degreeOfDifficultyArray;
        for (int i = 0; i < 5; ++i) {
            outputFileStream << std::endl << theTypeOfQuestions[i] << std::endl;
            // 这是一个顺序表，可以进行二分快速查找
            questions.readQuestionsFromFile(static_cast<std::string>("UpdateQuestionRepository/") + file[i]);
            int difficultyArray[5];
            for (int& item : difficultyArray) {
                // 使用随机数
                item = randomNumber();
            }

            ly::SequenceList<ly::Questions> questionsTemp;
            ly::Questions op;
            questionsTemp.addQuestions(op);
            for (int k = 0; k < questions.getLength(); ++k) {
                questionsTemp.addQuestions(questions.getElement()[k]);
            }

            int flag{0};
            int indexOfDifficultyArray{0};
            while (true) {
                if (indexOfDifficultyArray == 5) {
                    break;
                }
                // 折半查找
                int theNumberOfQuestions{questionsTemp.halfSearch(difficultyArray[indexOfDifficultyArray])};
                if (theNumberOfQuestions != 0) {
                    outputFileStream << questionsTemp.getElement()[theNumberOfQuestions].getQuestion() << std::endl
                                     << std::endl;
                    questionsTemp.remove(theNumberOfQuestions);
                    questionsTemp.infoOfQuestionsFile();
                    // 难度系数
                    degreeOfDifficulty += difficultyArray[indexOfDifficultyArray];
                    degreeOfDifficultyArray.addIntegerElement(difficultyArray[indexOfDifficultyArray]);
                } else {
                    ++flag;
                    // 极端情况: 如何没有一个难度系数相符的话退出该 while 语句
                    if (flag == 5) {
                        break;
                    }
                    ++indexOfDifficultyArray;
                    continue;
                }
                ++indexOfDifficultyArray;
            }

            for (int j = 0; j < flag; ++j) {
                outputFileStream << questionsTemp.getElement()[questionsTemp.getLength() - 1].getQuestion() << std::endl
                                 << std::endl;
                // 难度系数
                degreeOfDifficulty += questionsTemp.getElement()[questionsTemp.getLength() - 1].getDifficulty();
                degreeOfDifficultyArray.addIntegerElement(
                        questionsTemp.getElement()[questionsTemp.getLength() - 1].getDifficulty());
                questionsTemp.remove(static_cast<int>(questionsTemp.getLength() - 1));
                questionsTemp.infoOfQuestionsFile();
            }
            // 没有必要进行删除
            /*            // 抽取完题目后的题库
            questionsTemp.writeQuestionsToFile(i);*/
        }
        std::cout << "本套试题题目难度系数按题目顺序依次为: ";
        for (int i = 0; i < degreeOfDifficultyArray.getLength(); ++i) {
            std::cout << degreeOfDifficultyArray.getElement()[i] << " ";
        }
        std::cout << std::endl;
        return degreeOfDifficulty / 10.0;
    }


    void systemFeatures() {
        std::cout << "====================[ Math ]====================\n"
                  << "\t1. 进入题库\n"
                  << "\t2. 生成试卷\n"
                  << "\t3. 退出系统\n"
                  << "====================[ Math ]====================" << std::endl;
        std::cout << "输入选择: ";
    }


    void programController(int choice) {
        const char* file[5]{"calculationQuestions", "ApplicationQuestions", "CompletionQuestions", "choiceQuestions",
                            "brainQuestions"};
        if (choice == 1) {
            int questionType;
            while (true) {
                std::cout << "====================[ 题库 ]====================\n"
                          << "\t1. 计算题\n"
                          << "\t2. 应用题\n"
                          << "\t3. 填空题\n"
                          << "\t4. 判断题\n"
                          << "\t5. 脑筋急转弯题\n"
                          << "====================[ 题库 ]====================" << std::endl;

                std::cout << "输入想要查看的题目: ";
                std::cin >> questionType;
                ly::SequenceList<ly::Questions> questions;
                questions.readQuestionsFromFile(static_cast<std::string>("QuestionRepository/") +
                                                file[questionType - 1]);
                questions.infoOfQuestionsFile();
                ly::quickSort(0, static_cast<int>(questions.getLength() - 1), questions);
                std::cout << "----" << std::endl;
                questions.infoOfQuestionsFile();

                std::cout << "\n是否进行更新题库? (Y/y or N/n) ";
                char update;
                std::cin >> update;
                if (update == 'y' || update == 'Y') {
                    questions.updateQuestions();
                }
                std::cout << "\n是否添加题目? (Y/y or N/n)";
                char add;
                std::cin >> add;
                if (add == 'y' || add == 'Y') {
                    questions.readQuestionsFromFile(questions.addQuestionToFile(file, questionType));
                }
                std::cout << "\n是否删除题目? (Y/y or N/n)";
                char remove;
                std::cin >> remove;
                if (remove == 'y' || remove == 'Y') {
                    questions.remove().writeQuestionsToFile("QuestionRepository/", questionType);
                }

                ly::quickSort(0, static_cast<int>(questions.getLength() - 1), questions);
                questions.infoOfQuestionsFile();
                questions.writeQuestionsToFile("UpdateQuestionRepository/", questionType);
                std::cout << "\n是否退出题库? (Y/y or N/n) ";
                char exit;
                std::cin >> exit;
                if (exit == 'Y' || exit == 'y') {
                    break;
                }
            }
        } else if (choice == 2) {
            std::ofstream outputFileStream;
            ly::SequenceList<ly::Questions> questionsForTest;
            double degreeOfDifficulty{generationTest(outputFileStream, questionsForTest, file)};
            std::cout << "本套试题难度系数: " << degreeOfDifficulty << std::endl;
        }
    }


}


#endif // SEQUENCELIST_HPP
